/**
 * User: alesj
 * Date: 11.1.2006
 * Time: 12:17:17
 *
 * (C) Genera Lynx d.o.o.
 */

package com.alesj.blueberry.ejb.core.impl;

import com.alesj.blueberry.ejb.common.AbstractLogManager;
import com.alesj.blueberry.ejb.core.CoreManager;
import com.alesj.blueberry.ejb.dao.BasicDAO;
import com.alesj.blueberry.par.basic.ErrorLog;
import com.alesj.blueberry.par.basic.GameObject;
import com.alesj.blueberry.par.basic.RequestEntry;
import com.alesj.blueberry.par.basic.ValidationError;
import com.alesj.blueberry.spring.games.Strategy;
import com.alesj.blueberry.spring.parsing.ParseException;
import com.alesj.blueberry.spring.result.Commiter;
import com.alesj.blueberry.spring.result.CommiterHolder;
import com.alesj.blueberry.spring.result.DefaultResult;
import com.alesj.blueberry.spring.result.MessageInfo;
import com.alesj.blueberry.spring.timestamp.Timestamper;
import com.alesj.blueberry.spring.validation.ValidationChain;
import com.alesj.blueberry.spring.validation.ValidationResult;
import org.jboss.annotation.spring.Spring;

import javax.annotation.EJB;
import javax.ejb.Stateless;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Main entry point for the whole application.
 * Handles different products/strategies.
 * Uses timestamper object to verify time correctness.
 *
 * DAO access is separated from business logic - in BasicDAO.
 * @see BasicDAO
 *
 * @author <a href="mailto:ales.justin@genera-lynx.com">Ales Justin</a>
 */
@Stateless
//@SecurityDomain(value = "other")
public class CoreManagerImpl extends AbstractLogManager implements CoreManager {

    @EJB
    private BasicDAO basicDAO;

    @Spring(jndiName = "bb-pojo", bean = "timestamper")
    private Timestamper timestamper;

    @Spring(jndiName = "bb-pojo", bean = "strategies")
    private Strategy[] strategies;

    /**
     * Find first matching strategy.
     *
     * @param number
     * @param message
     * @return strategy or null if no matching strategy found
     */
    protected Strategy getStrategy(String number, String message) {
        int strategyIndex = -1;
        int possibleStrategies = 0;
        for(int i = 0; i < strategies.length; i++) {
            Strategy strategy = strategies[i];
            if (strategy.useStrategy(number, message)) {
                strategyIndex = i;
                possibleStrategies++;
            }
        }
        if (possibleStrategies != 1) {
            getLog().warn(
                    "Invalid possible strategy size: " + possibleStrategies +
                    ";[" + number + ", " + message + "]");
            return null;
        }
        return strategies[strategyIndex];
    }

    // session methods

    /**
     * Main method.
     * Finds appropriate strategy to handle incoming message.
     * Validates with found strategy.
     * Creates game object.
     * Invokes all strategy commiters.
     */
//    @RolesAllowed(value = {"smpp_service"})
    public MessageInfo processRequest(String messageId, String number, String messageText) {
        getLog().info("Received request: " + number + " / " + messageText);
        long timestamp = timestamper.timestamp();
        RequestEntry entry = new RequestEntry(timestamp, messageId, number, messageText);
        basicDAO.saveRequestEntry(entry);
        getLog().info("Processing request: " + entry);
        Strategy strategy = getStrategy(number, messageText);
        if (strategy == null) {
            return new DefaultResult(MessageInfo.STRATEGY_KEY, new Object[]{messageText});
        }
        getLog().info("Using game strategy: " + strategy.getGame().getName());
        ValidationChain validationChain = strategy.validators();
        ValidationResult validationResult = validationChain.validate(timestamp, number, messageText);
        if (validationResult != null) {
            getLog().warn("Validation error: " + validationResult);
            ValidationError validationError = new ValidationError();
            validationError.setRequestEntry(entry);
            entry.setValidationError(validationError);
            validationError.setErrorType(validationResult.getKey());
            basicDAO.saveValidationError(validationError);
            return validationResult;
        }
        Map<String, Object> resultMap;
        try {
            resultMap = strategy.getParser().parse(messageText);
        } catch (ParseException pe) {
            getLog().warn("Illegal message / parse exception: " + pe.getKey() + " / " + pe.getValue());
            return pe;
        }
        GameObject gameObject = strategy.createGameObject();
        gameObject.setRequestEntry(entry);
        entry.setGameObject(gameObject);
        gameObject.fillResultMap(resultMap);
        double price = basicDAO.findPrice(gameObject, strategy.getGame());
        gameObject.setPrice(price);
        getLog().info("Saving game object: " + gameObject.info());
        basicDAO.saveGameObject(gameObject);
        int i = 0;
        getLog().info("Preparing to commit game.");
        CommiterHolder commiterHolder = strategy.getCommiters();
        List<Commiter> commiters = new ArrayList<Commiter>(commiterHolder.getCommiters());
        try {
            for(; i < commiters.size(); i++) {
                commiters.get(i).commit(gameObject);
            }
        } catch (Throwable t) {
            getLog().error("Failed to commit game: " + t.getMessage() + ", rolling back.");
            ErrorLog errorLog = new ErrorLog(entry, t.getMessage(), timestamper.serverDate(), i);
            basicDAO.saveErrorLog(errorLog);
            for(; i >= 0; i--) {
                commiters.get(i).rollback(gameObject);
            }
            return new DefaultResult(MessageInfo.FAILURE_KEY, new Object[]{gameObject});
        }
        return new DefaultResult(MessageInfo.SUCCESS_KEY, new Object[]{gameObject});
    }

    // CRUD methods (delegate them to DAO)

    public void updateSubmit(String messageId, String number) {
        basicDAO.updateSubmit(messageId, number);
    }

    public void updateDelivery(String messageId, String number) {
        basicDAO.updateDelivery(messageId, number);
    }

    public void saveErrorLog(String messageId, String number, String exMessage) {
        basicDAO.saveErrorLog(messageId, number, exMessage);
    }

}
